//@author A0099223B

import java.util.ArrayList;
import java.util.Date;

public class ListParser extends Parser {
	private static final String MESSAGE_WRONG_DATE_FORMAT = "\"%1$s\" is not an understood date";
	private static final String MESSAGE_LIST_DATE_WRONG_DETAILS = "Please use \"list [day]\" to view tasks for a day";
	private static final String MESSAGE_DONE_TAG_CLASH = "Only one of these can be present: "
			+ TAG_DONE + ", " + TAG_NOTDONE;
	private static final String MESSAGE_KEYWORD_SEARCH_SEPERATION = TAG_KEYWORD
			+ " should be used seperately from " + TAG_TITLE + " "
			+ TAG_START_TIME + " " + TAG_END_TIME + " " + TAG_PLACE + " "
			+ TAG_DESCRIPTION + " " + TAG_PROJECT;
	private static final String NO_DONE_TAG = null;
	private static final ArrayList<String> UNUSABLE_TAGS = new ArrayList<String>() {
		{
			add(TAG_ID);
		}
	};

	protected static Command parse(String userInput)
			throws InvalidInputException {
		assert (userInput.toLowerCase().startsWith(COMMAND_LIST));

		ListCommand listCommand = new ListCommand();

		ArrayList<String> unparsedWords = getWordsInCommandDetails(userInput);
		checkTags(unparsedWords);

		searchForAndProcessDoneTag(listCommand, unparsedWords);

		if (unparsedWords.isEmpty()) {
			createListAllCommand(listCommand);
		} else if (isBeginningWithTag(unparsedWords)) {
			fillFilterDetails(listCommand, unparsedWords);
		} else {
			setAsListDateCommand(listCommand, unparsedWords);
		}

		return listCommand;
	}

	private static void checkTags(ArrayList<String> unparsedWords)
			throws InvalidInputException {

		checkForUnusableStringTags(UNUSABLE_TAGS, unparsedWords, COMMAND_LIST);
		checkForRepeatingTags(unparsedWords);
	}

	private static void searchForAndProcessDoneTag(ListCommand listCommand,
			ArrayList<String> unparsedWords) throws InvalidInputException {
		String doneTag = getDoneTag(unparsedWords);
		processDoneTag(doneTag, listCommand, unparsedWords);
	}

	// returns null if both done tags are missing
	// throws exception if both are present
	private static String getDoneTag(ArrayList<String> unparsedWords)
			throws InvalidInputException {
		boolean isDoneTagePresent = false;
		boolean isNotDoneTagPresent = false;

		for (String word : unparsedWords) {
			if (word.equalsIgnoreCase(TAG_DONE)) {
				isDoneTagePresent = true;
			}

			if (word.equalsIgnoreCase(TAG_NOTDONE)) {
				isNotDoneTagPresent = true;
			}
		}

		if (isDoneTagePresent && isNotDoneTagPresent) {
			throw new InvalidInputException(MESSAGE_DONE_TAG_CLASH);
		}

		if (isDoneTagePresent) {
			return TAG_DONE;
		}

		if (isNotDoneTagPresent) {
			return TAG_NOTDONE;
		}

		return NO_DONE_TAG;
	}

	private static void processDoneTag(String doneTag,
			ListCommand listCommand, ArrayList<String> unparsedWords)
			throws InvalidInputException {
		if (doneTag != NO_DONE_TAG) {
			removeDoneTag(unparsedWords, doneTag);
			setDisplayDoneState(listCommand, doneTag);
		}
	}

	private static void removeDoneTag(ArrayList<String> unparsedWords,
			String doneTag) throws InvalidInputException {
		int index = getIndexOfTag(doneTag, unparsedWords);

		if (index != ARRAY_INDEX_NOT_FOUND) {
			removeFromArrayList(unparsedWords, index);
		}

	}

	private static void removeFromArrayList(ArrayList<String> unparsedWords,
			int tagIndex) throws InvalidInputException {

		ArrayList<String> wordsAfterTag = new ArrayList<String>(
				unparsedWords.subList(tagIndex + 1, unparsedWords.size()));

		String extractedWords = extractWordsBeforeNextTag(wordsAfterTag);

		ArrayList<String> wordBeforeTag = new ArrayList<String>(
				unparsedWords.subList(0, tagIndex));

		combineInOriginalList(unparsedWords, wordBeforeTag, wordsAfterTag);

		checkIfTagSupposeToAppearBeforeExtractedWords(unparsedWords,
				extractedWords);
	}

	private static void checkIfTagSupposeToAppearBeforeExtractedWords(
			ArrayList<String> unparsedWords, String extractedWords)
			throws InvalidInputException {

		if (!isEmptyString(extractedWords)) {
			if (isBeginningWithTag(unparsedWords)) {
				// If there are other tags, it is either
				// keyword search or filtering command,
				// thus there should not be untagged words after
				// done tag
				throw new InvalidInputException(String.format(
						MESSAGE_UNPRECEEDED_BY_TAG, extractedWords));
			} else {
				// if there are no other tags,
				// it is list [date] command so replace the words
				unparsedWords.addAll(getSetofWords(extractedWords));
			}
		}
	}

	private static void setDisplayDoneState(ListCommand listCommand,
			String doneTag) {
		if (doneTag.equalsIgnoreCase(TAG_DONE)) {
			listCommand.displayDoneStates = ListCommand.DisplayDoneState.DONE;
		}
		if (doneTag.equalsIgnoreCase(TAG_NOTDONE)) {
			listCommand.displayDoneStates = ListCommand.DisplayDoneState.NOTDONE;
		}
	}

	private static void createListAllCommand(ListCommand listCommand) {
		listCommand.isListAll = true;
	}

	private static void fillFilterDetails(ListCommand listCommand,
			ArrayList<String> unparsedWords) throws InvalidInputException {

		while (!unparsedWords.isEmpty()) {
			String tag = removeFirstWordFromArray(unparsedWords);

			if (isTaggedAsProject(tag)) {
				listCommand.projectFilter = getProjectName(tag);

			} else {
				switchToCorrectAttribute(listCommand, unparsedWords, tag);
			}
		}

		checkForKeywordSearchClash(listCommand);
		checkForReversedDates(listCommand.startTimeFilter,
				listCommand.endTimeFilter);

	}

	private static void switchToCorrectAttribute(ListCommand listCommand,
			ArrayList<String> unparsedWords, String tag)
			throws InvalidInputException {

		checkIfTag(tag);
		String value = extractWordsBeforeNextTag(unparsedWords);

		checkForEmptyValue(value, tag);

		switch (tag.toLowerCase()) {
		case TAG_KEYWORD:
			listCommand.keyWordSearch = value;
			break;
		case TAG_START_TIME:
			listCommand.startTimeFilter = parseTime(value, TAG_START_TIME);
			break;
		case TAG_END_TIME:
			listCommand.endTimeFilter = parseTime(value, TAG_END_TIME);
			break;
		case TAG_PLACE:
			listCommand.placeFilter = value;
			break;
		case TAG_DESCRIPTION:
			listCommand.descriptionFilter = value;
			break;
		case TAG_TITLE:
			listCommand.titleFilter = value;
			break;
		}
	}

	private static void checkForKeywordSearchClash(ListCommand listCommand)
			throws InvalidInputException {
		if (listCommand.keyWordSearch != Task.EMPTY_STRING) {
			if (listCommand.startTimeFilter != Task.EMPTY_TIME
					|| listCommand.endTimeFilter != Task.EMPTY_TIME
					|| listCommand.placeFilter != Task.EMPTY_STRING
					|| listCommand.descriptionFilter != Task.EMPTY_STRING
					|| listCommand.titleFilter != Task.EMPTY_STRING
					|| listCommand.projectFilter != Task.EMPTY_STRING) {
				throw new InvalidInputException(
						MESSAGE_KEYWORD_SEARCH_SEPERATION);
			}
		}

	}

	private static void setAsListDateCommand(ListCommand listCommand,
			ArrayList<String> unparsedWords) throws InvalidInputException {
		assert (!unparsedWords.isEmpty());

		String dateString = extractWordsBeforeNextTag(unparsedWords);
		ensureNoTagsAfter(unparsedWords);
		setTimeFilterValues(listCommand, dateString);
		checkIfItsDateOnly(listCommand);
	}

	private static void ensureNoTagsAfter(ArrayList<String> unparsedWords)
			throws InvalidInputException {
		if (isBeginningWithTag(unparsedWords)) {
			throw new InvalidInputException(MESSAGE_LIST_DATE_WRONG_DETAILS);
		}

	}

	private static void setTimeFilterValues(ListCommand listCommand,
			String dateString) throws InvalidInputException {
		try {
			listCommand.startTimeFilter = parseTime(dateString, TAG_START_TIME);
			listCommand.endTimeFilter = parseTime(dateString, TAG_END_TIME);
		} catch (InvalidInputException e) {
			// Change message
			throw new InvalidInputException(String.format(
					MESSAGE_WRONG_DATE_FORMAT, dateString));
		}
	}

	// Due to the behaviour of parseTime, any list [parsable time/date]
	// will go through
	// However only in the case of a date-only input would
	// listCommand.startTimeFilter = parseTime(dateString,TAG_START_TIME);
	// listCommand.endTimeFilter = parseTime(dateString,TAG_START_TIME);
	// give different times
	private static void checkIfItsDateOnly(ListCommand listCommand)
			throws InvalidInputException {
		if (listCommand.startTimeFilter.equals(listCommand.endTimeFilter)) {
			throw new InvalidInputException(MESSAGE_LIST_DATE_WRONG_DETAILS);
		}

	}

	private static boolean isBeginningWithTag(ArrayList<String> unparsedWords) {
		if (unparsedWords.isEmpty()) {
			return false;
		}

		String firstWord = unparsedWords.get(ARRAY_INDEX_FIRST);
		if (isNotTag(firstWord)) {
			return false;
		}
		return true;
	}

}
